using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using LINQPad;
using Xtensive.Orm;
using Xtensive.Orm.Services;
using FieldInfo = Xtensive.Orm.Model.FieldInfo;

namespace LINQPadDriver
{
  public class PersistentMemberProvider : ICustomMemberProvider
  {
    #region Nested type: EntitySetPresenter

    private class EntitySetPresenter:ICustomMemberProvider
    {
      private readonly EntitySetBase entitySet;
      private readonly DumpContainer container;
      private readonly ListDictionary properties = new ListDictionary();
      private readonly DumpContainer itemsContainer;
      private readonly List<Entity> entities;
      private readonly int count;
      private static PropertyInfo currentServer;
      private static MethodInfo runOnMessageLoopThread;

      public EntitySetPresenter(EntitySetBase entitySet, DumpContainer container)
      {
        this.entitySet = entitySet;
        this.container = container;
        var q = ((IQueryable<Entity>) entitySet);
        count = q.Count();
        entities = q.Take(10).ToList();
        itemsContainer = new DumpContainer();
        Update();
      }

      static EntitySetPresenter()
      {
        var assembly = typeof(DumpContainer).Assembly;
        var type = assembly.GetType("LINQPad.ExecutionModel.Server");
        currentServer = type.GetProperty("CurrentServer");
        runOnMessageLoopThread = type.GetMethod("RunOnMessageLoopThread");
      }

      private void Update()
      {
        if (entities.Count==count) {
          Action action = () => {
            container.Content = entities;
          };
          var server = currentServer.GetValue(null, new object[0]);
          runOnMessageLoopThread.Invoke(server, new object[] { action });
          properties.Clear();
          return;
        }
        properties["Count"] = count;
        itemsContainer.Content = entities;
        properties["Items"] = itemsContainer;
        properties["Next10"] = new DumpContainer(
          new Hyperlinq(
            () => {
              entities.AddRange(((IQueryable<Entity>)entitySet).Skip(entities.Count).Take(10));
              Update();
            },
            "Load next 10"));
        properties["All"] = new DumpContainer(
          new Hyperlinq(
            () => {
              entities.AddRange(((IQueryable<Entity>) entitySet).Skip(entities.Count).Take(count - entities.Count));
              Update();
            },
            "Load all"));
      }


      public override string ToString()
      {
        return "";
      }

      public IEnumerable<string> GetNames()
      {
        return properties.Keys.Cast<string>();
      }

      public IEnumerable<Type> GetTypes()
      {
        return properties.Values.OfType<object>().Select(a => a.GetType());
      }

      public IEnumerable<object> GetValues()
      {
        return properties.Values.OfType<object>();
      }
    }

    #endregion

    private readonly Persistent entity;

    public IEnumerable<string> GetNames()
    {
      return GetFields(entity).Select(f => f.OriginalName);
    }

    public IEnumerable<Type> GetTypes()
    {
      return GetFields(entity).Select(f => f.ValueType);
    }

    public IEnumerable<object> GetValues()
    {
      Session session = Session.Current;
      var persistentAccessor = new DirectPersistentAccessor(session);
      if (LINQPadDriver.EntitiesCache==null)
        LINQPadDriver.EntitiesCache = DirectStateAccessor.Get(session).ToDictionary(a => a.Key);

      foreach (FieldInfo f in GetFields(entity)) {
        if (f.IsEntity) {
          Key key = persistentAccessor.GetReferenceKey(entity, f);
          if (key==null)
            yield return null;
          else if (LINQPadDriver.EntitiesCache.GetOrDefault(key)!=null)
            yield return LINQPadDriver.EntitiesCache.GetOrDefault(key);
          else {
            var container = new DumpContainer();
            FieldInfo field = f;
            container.Content =
              new Hyperlinq(
                () =>
                {
                  container.Content = persistentAccessor.GetFieldValue(entity, field);
                },
                "Key: " + key);
            yield return container;
          }
        }
        else if (f.IsEntitySet) {
          var set = (EntitySetBase)entity[f.Name];
          EntitySetStateAccessor state = DirectStateAccessor.Get(set);
          if (state.IsFullyLoaded)
            yield return entity[f.Name];
          else {
            var container = new DumpContainer();
            container.Content =
              new Hyperlinq(
                () => {
                  container.Content = new EntitySetPresenter(set, container);
                },
                "show");
            yield return container;
          }
        }
        else {
          PersistentStateAccessor persistentState = DirectStateAccessor.Get(entity);
          PersistentFieldState state = persistentState.GetFieldState(f);
          if (state!=PersistentFieldState.Loaded && state!=PersistentFieldState.Modified) {
            var container = new DumpContainer();
            FieldInfo field = f;
            container.Content =
              new Hyperlinq(() => { container.Content = persistentAccessor.GetFieldValue(entity, field); }, "load");
            yield return container;
          }
          else
            yield return persistentAccessor.GetFieldValue(entity, f);
        }
      }
    }

    #region Non-public methods

    private IEnumerable<FieldInfo> GetFields(Persistent entity)
    {
      return entity.TypeInfo.Fields.Where(a => !a.IsNested && a.Name!="TypeId");
    }

    #endregion

    public PersistentMemberProvider(Persistent entity)
    {
      this.entity = entity;
    }
  }
}